#include <iostream>

using namespace std;
#define private public
#define protected public


#include <core/opt_traits.hpp>
#include <boost/python.hpp>
using namespace boost::python;


#include <boost/ref.hpp>
#include <boost/utility.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <core/fitter.hpp>
#include <data_sets/default_data_set.hpp>
#include <data_sets/sorted_data_set.hpp>
#include <core/freeze_param.hpp>
#include <misc/bootstrap.hpp>
#include <statistics/chisq.hpp>
#include <statistics/cstat.hpp>
#include <statistics/leastsq.hpp>
#include <methods/powell/powell_method.hpp>
#include <methods/gsl_simplex/gsl_simplex.hpp>
#include <methods/aga/aga.hpp>
#include <vector>
#include <models/add_model.hpp>
#include <opt_serialization.hpp>
using namespace boost;
using namespace std;
using namespace opt_utilities;


typedef fitter<double,double,vector<double>,double,std::string> pyfitter;
typedef statistic<double,double,vector<double>,double,std::string> pystatistic;
typedef param_modifier<double,double,vector<double>,
		       std::string>pyparam_modifier;
typedef data<double,double> pydata;
typedef data_set<double,double> pydata_set;
typedef param_info<vector<double>,std::string> pyparam_info;
typedef chisq<double,double,vector<double>,double,std::string> pychisq;
typedef cstat<double,double,vector<double>,double,std::string> pycstat;
typedef leastsq<double,double,vector<double>,double,std::string> pyleastsq;
typedef opt_method<double,vector<double> > pyopt_method;
typedef powell_method<double,vector<double> > pypowell_method;
typedef aga_method<double,vector<double> > pyaga_method;
typedef gsl_simplex<double,vector<double> > pygsl_simplex;
typedef optimizer<double,vector<double> > pyoptimizer;
typedef func_obj<double,vector<double> > pyfunc_obj;
typedef model<double,double,vector<double>,std::string> pymodel;
typedef freeze_param<double,double,vector<double>,std::string> pyfreeze_param;
typedef bootstrap<double,double,vector<double>,double,std::string> pybootstrap;
typedef optimizer_info<double,vector<double> > pyoptimizer_info;
typedef fitter_info<double,double,vector<double>,double,string> pyfitter_info;
typedef param_info_info<vector<double>,string> pyparam_info_info;


void freeze(pyfitter& fit,const string& pname)
{
  freeze_param<double,double,vector<double> > fp(pname);
  try
    {
      dynamic_cast<freeze_param<double,double,vector<double> >& >
	(fit.get_param_modifier())+=fp;
    }
  catch(opt_exception& e)
    {
      fit.set_param_modifier(fp);
    } 
}

void thaw(pyfitter& fit,const string& pname)
{
  freeze_param<double,double,vector<double> > fp(pname);
  try
    {
      dynamic_cast<freeze_param<double,double,vector<double> >& >
	(fit.get_param_modifier())-=fp;
    }
  catch(opt_exception& e)
    {
      //  fit.set_param_modifier(fp);
    } 
}



class func_obj_wrapper
  :public func_obj<double,vector<double> >,
   public wrapper<func_obj<double,vector<double> > >
{
private:
  func_obj_wrapper* do_clone()const
  {
    return this->get_override("do_clone")();
  }
  
  void do_destroy()
  {
    override foo=this->get_override("do_destroy");
    if(foo)
      {
	foo();
      }
    else
      {
	default_do_destroy();
      }
  }

  void default_do_destroy()
  {
    this->func_obj<double,vector<double> >::do_destroy();
  }
  
  double do_eval(const vector<double>& p)
  {
    return this->get_override("do_eval")(p);
  }

public:

  const char* do_get_type_name()const
  {
    //return "wrapper";
    return this->get_override("do_get_type_name")();
  }
};

const char* get_type_name(const func_obj_wrapper& obj)
{
  return obj.get_type_name();
}

class model_wrapper
  :public model<double,double,vector<double> >,
   public wrapper<model<double,double,vector<double> > >
{
private:
  //object obj;
public:
  model_wrapper* do_clone()const
  {
    return this->get_override("do_clone")();
  }

  void do_destroy()
  {
    cout<<"model destroyed..."<<endl;
    this->get_override("do_destroy")();
  }

  double do_eval(const double& x,const vector<double>& p)
  {
    return this->get_override("do_eval")(x,p);
  }

  void add_param_info(const char* name,double v)
  {
    this->push_param_info(pyparam_info(name,v));
  }

  const char* do_get_type_name()const
  {
    return this->get_override("do_get_type_name")();
  }

  virtual ~model_wrapper()
  {
    cout<<"model_destr"<<endl;
  }
  
};


const char* get_type_name(const model_wrapper& obj)
{
  return obj.get_type_name();
}

class statistic_wrapper
  :public statistic<double,double,vector<double>,double>,
   public wrapper<statistic<double,double,vector<double>,double> >
{
 public:
  statistic_wrapper* do_clone()const
  {
    return this->get_override("do_clone")();
  }
  
  void do_destroy()
  {
    cout<<"statistic destroyed"<<endl;
  }

  double do_eval(const vector<double>& p)
  {
    return this->get_override("do_eval")(p);
  }

  const char* do_get_type_name()const
  {
    return this->get_override("do_get_type_name")();
  }
  
};
	  

const char* get_type_name(const statistic_wrapper& obj)
{
  return obj.get_type_name();
}


typedef default_data_set<double,double> pydefault_data_set;
typedef sorted_data_set<double,double> pysorted_data_set;

void (pyfitter::*spm0)()=&pyfitter::clear_param_modifier;
void (pyfitter::*spm1)(const param_modifier<double,double,
		       std::vector<double> >&)=&pyfitter::set_param_modifier;
void (model_wrapper::*spm2)
(const param_modifier<double,double,vector<double> >&)
=&model_wrapper::set_param_modifier;
void (model_wrapper::*spm3)()=&model_wrapper::clear_param_modifier;
const pyparam_info& (model_wrapper::*gpi0)
  (size_t)const=&model_wrapper::get_param_info;
const pyparam_info& (model_wrapper::*gpi1)
  (const std::string&)const=&model_wrapper::get_param_info;


BOOST_PYTHON_MODULE(core)
{
  def("freeze",(void(*)(pyfitter&,const string&))freeze);
  def("thaw",(void(*)(pyfitter&,const string&))thaw);
  def("get_fo_name",(const char* (*)(const func_obj_wrapper&))get_type_name);
  def("get_st_name",(const char* (*)(const statistic_wrapper&))get_type_name);
  def("get_md_name",(const char* (*)(const model_wrapper&))get_type_name);

  def("get_optimizer_info",&get_optimizer_info<double,vector<double> >);
  def("get_fitter_info",&get_fitter_info<double,double,vector<double>,double,std::string>);

  def("form_optimizer",&form_optimizer<double,vector<double> >);
  def("form_fitter",&form_fitter<double,double,vector<double>,double,std::string>);

  def("register_func_obj",  &register_func_obj<double,vector<double> >);
  def("register_opt_method",&register_opt_method<double,vector<double> >);
  def("register_statistic",&register_statistic<double,double,vector<double>,double,std::string>);
  def("register_param_modifier",&register_param_modifier<double,double,vector<double>,string>);
  def("register_data_set",&register_data_set<double,double>);
  def("register_model",&register_model<double,double,vector<double>,std::string>);
  
  

  class_<std::vector<double> >("pyvec")
    .def(vector_indexing_suite<std::vector<double> >())
    .def(init<int>())
    .def("resize",&vector<double>::resize)
    .def("size",&vector<double>::size)
    ;
  
  class_<pyfreeze_param>("pyfreeze_param")
    .def(init<const string&>())
    .def("__iadd__",&pyfreeze_param::operator+=,
	 return_internal_reference<1>())
    .def("__isub__",&pyfreeze_param::operator-=,
	 return_internal_reference<1>())
    ;

  class_<func_obj_wrapper,boost::noncopyable>("func_obj_wrapper")
    .def("do_clone",pure_virtual(&func_obj_wrapper::do_clone),
	 return_value_policy<reference_existing_object>())
    .def("clone",&func_obj_wrapper::clone,
	 return_value_policy<reference_existing_object>())
    .def("do_eval",pure_virtual(&func_obj_wrapper::do_eval))
    .def("do_get_type_name",pure_virtual(&func_obj_wrapper::do_get_type_name))
    .def("eval",&func_obj_wrapper::eval)
    .def("do_destroy",pure_virtual(&func_obj_wrapper::do_destroy))
    .def("destroy",&func_obj_wrapper::destroy)
    .def("__call__",&func_obj_wrapper::eval)
    
    ;

  class_<pyoptimizer>("pyoptimizer")
    .def(init<>())
    .def("set_func_obj",&pyoptimizer::set_func_obj)
    .def("set_opt_method",&pyoptimizer::set_opt_method)
    .def("set_precision",&pyoptimizer::set_precision)
    .def("set_start_point",&pyoptimizer::set_start_point)
    .def("eval",&pyoptimizer::eval)
    .def("optimize",&pyoptimizer::optimize)
    ;
  
  
  class_<pyopt_method,boost::noncopyable>("pyopt_method",no_init);
  
  class_<statistic_wrapper,boost::noncopyable>("pystatistic")
    .def("do_clone",pure_virtual(&statistic_wrapper::do_clone),
	 return_value_policy<reference_existing_object>())
    .def("clone",&statistic_wrapper::clone,
	 return_value_policy<reference_existing_object>())
    .def("do_destroy",pure_virtual(&statistic_wrapper::do_destroy))
    .def("destroy",&statistic_wrapper::destroy)
    .def("do_eval",pure_virtual(&statistic_wrapper::do_eval))
    .def("do_get_type_name",pure_virtual(&statistic_wrapper::do_get_type_name))
    .def("eval",&statistic_wrapper::eval)
    .def("get_data_set",&statistic_wrapper::get_data_set,
	 return_internal_reference<1>())
    .def("eval_model",&statistic_wrapper::eval_model)
    ;
  
  class_<model_wrapper,boost::noncopyable>("model_wrapper")
    .def("__str__",&model_wrapper::to_string)
    .def("do_clone",pure_virtual(&model_wrapper::do_clone),
	 return_value_policy<reference_existing_object>())
    .def("do_destroy",pure_virtual(&model_wrapper::do_destroy))
    .def("destroy",&model_wrapper::destroy)
    .def("clone",&model_wrapper::clone,
	 return_value_policy<reference_existing_object>())
    .def("do_eval",pure_virtual(&model_wrapper::do_eval))
    .def("do_get_type_name",pure_virtual(&model_wrapper::do_get_type_name))
    .def("eval",&model_wrapper::eval)
    .def("push_param_info",&model_wrapper::add_param_info)
    .def("clear_param_info",&model_wrapper::clear_param_info)
    .def("get_num_params",&model_wrapper::get_num_params)
    .def("get_param_info",gpi1,
	 return_internal_reference<1>())
    .def("get_param_info",gpi0,
	 return_internal_reference<1>())

    ;
  
  
  

  class_<pychisq,bases<pystatistic> >("pychisq")
    .def("verbose",&pychisq::verbose)
    ;

  class_<pycstat,bases<pystatistic> >("pycstat")
    .def("verbose",&pychisq::verbose)
    ;

  class_<pyleastsq,bases<pystatistic> >("pyleastsq")
    .def("verbose",&pyleastsq::verbose)
    ;


  class_<pypowell_method,bases<pyopt_method> >("pypowell_method");
  class_<pyaga_method,bases<pyopt_method> >("pyaga_method");
  class_<pygsl_simplex,bases<pyopt_method> >("pygsl_simplex")
    .def("set_precision",&pygsl_simplex::set_precision);

  //implicitly_convertible<pydata&,const pydata&>();
  class_<pydata_set,boost::noncopyable>("pydata_set",no_init)
    .def("get_data",&pydata_set::get_data,
	 return_internal_reference<1>())
    .def("size",&pydata_set::size)
    .def("add_data",&pydata_set::add_data)
    .def("clear",&pydata_set::clear)
    ;
  
  
  class_<pyparam_info>("pyparam_info")
    .def(init<>())
    .def(init<const std::string&,const double&>())
    .def(init<pyparam_info>())
    .def("set_value",&pyparam_info::set_value)
    .def("set_lower_limit",&pyparam_info::set_lower_limit)
    .def("set_upper_limit",&pyparam_info::set_upper_limit)
    .def("set_name",&pyparam_info::set_name)
    .def("get_name",&pyparam_info::get_name,
	 return_value_policy<copy_const_reference>())
    .def("get_value",&pyparam_info::get_value,
	 return_value_policy<copy_const_reference>())
    .def("get_lower_limit",&pyparam_info::get_lower_limit,
	 return_value_policy<copy_const_reference>())
    .def("get_upper_limit",&pyparam_info::get_upper_limit,
	 return_value_policy<copy_const_reference>())
    ;
  
  
  
  //class_<data_set_wrapper>("pydata_set");
  

  class_<pydefault_data_set,bases<pydata_set> >("pydefault_data_set")
    .def("add_data",&pydefault_data_set::add_data)
    .def("clear",&pydefault_data_set::clear)
    ;

  class_<pysorted_data_set,bases<pydata_set> >("pysorted_data_set")
    .def("add_data",&pysorted_data_set::add_data)
    .def("clear",&pysorted_data_set::clear)
    ;
 
  class_<pyfitter>("pyfitter")
    .def(init<>())
    .def(init<pyfitter>())
    .def("eval_model",&pyfitter::eval_model)
    .def("eval_model_raw",&pyfitter::eval_model_raw)
    .def("set_model",&pyfitter::set_model)
    .def("set_opt_method",&pyfitter::set_opt_method)
    .def("set_statistic",&pyfitter::set_statistic)
    .def("get_param_modifier",(pyparam_modifier& (pyfitter::*)())&pyfitter::get_param_modifier,
	 return_internal_reference<1>())
    .def("get_data_set",(pydata_set& (pyfitter::*)())&pyfitter::get_data_set,
	 return_internal_reference<1>())
    .def("set_param_value",
	 (void (pyfitter::*)(const vector<double>&))&pyfitter::set_param_value)
    .def("set_param_value",
	 (void (pyfitter::*)
	  (const string&,const double&))&pyfitter::set_param_value)
    .def("set_param_lower_limit",
	 (void (pyfitter::*)
	  (const string&,const double&))&pyfitter::set_param_lower_limit)
    .def("set_param_upper_limit",
	 (void (pyfitter::*)
	  (const string&,const double&))&pyfitter::set_param_upper_limit)
    .def("get_param_value",&pyfitter::get_param_value)
    .def("get_param_lower_limit",&pyfitter::get_param_lower_limit)
    .def("get_param_upper_limit",&pyfitter::get_param_upper_limit)
    .def("set_param_modifier",spm1)
    .def("set_param_modifier",spm0)
    .def("get_num_params",&pyfitter::get_num_params)
    .def("get_param_info",
	 (const param_info<vector<double> >& 
	  (pyfitter::*)
	  (const std::string& pname)const)&pyfitter::get_param_info,
	 return_internal_reference<1>())
    .def("get_param_info",
	 (const param_info<vector<double> >& 
	  (pyfitter::*)(size_t)const)&pyfitter::get_param_info,
	 return_internal_reference<1>())
    .def("report_param_status",&pyfitter::report_param_status)
    .def("load_data",&pyfitter::load_data)
    .def("fit",&pyfitter::fit)
    .def("get_all_params",&pyfitter::get_all_params)
    .def("set_precision",&pyfitter::set_precision)
    //.def("get_data_set",&pyfitter::get_data_set)
    ;

  class_<pybootstrap>("pybootstrap")
    .def(init<>())
    .def("set_fitter",&pybootstrap::set_fitter)
    .def("sample",(void (pybootstrap::*)(int))&pybootstrap::sample)
    .def("sample",(std::vector<double> (pybootstrap::*)())&pybootstrap::sample)
    .def("get_param",&pybootstrap::get_param,
	 return_value_policy<copy_const_reference>())
    .def("interval",&pybootstrap::interval);
    ;

  class_<pydata>("pydata")
    .def(init<>())
    .def(init<pydata>())
    .def(init<double,double,double,
	 double,double,double>())
    .def("set_x",&pydata::set_x)
    .def("set_x_lower_err",&pydata::set_x_lower_err)
    .def("set_x_upper_err",&pydata::set_x_upper_err)
    .def("set_y",&pydata::set_y)
    .def("set_y_lower_err",&pydata::set_y_lower_err)
    .def("set_y_upper_err",&pydata::set_y_upper_err)
    .def("get_x",&pydata::get_x,
	 return_value_policy<copy_const_reference>())
    .def("get_y",&pydata::get_y,
      return_value_policy<copy_const_reference>())
      .def("get_x_lower_err",&pydata::get_x_lower_err,
      return_value_policy<copy_const_reference>())
    .def("get_y_lower_err",&pydata::get_y_lower_err,
	 return_value_policy<copy_const_reference>())
    .def("get_x_upper_err",&pydata::get_x_upper_err,
	 return_value_policy<copy_const_reference>())
    .def("get_y_upper_err",&pydata::get_y_upper_err,
	 return_value_policy<copy_const_reference>())
    ;
  
  class_<pyoptimizer_info>("optimizer_info",init<>())
    .def_readwrite("func_obj_name",&pyoptimizer_info::func_obj_name)
    .def_readwrite("opt_method_name",&pyoptimizer_info::opt_method_name)
    .def_readwrite("precision",&pyoptimizer_info::precision)
    .def_readwrite("start_point",&pyoptimizer_info::start_point)
    .def_readwrite("lower_limit",&pyoptimizer_info::lower_limit)
    .def_readwrite("upper_limit",&pyoptimizer_info::upper_limit)
    ;
  
  class_<pyparam_info_info>("param_info_info",init<>())
    .def_readwrite("name",&pyparam_info_info::name)
    .def_readwrite("value",&pyparam_info_info::value)
    .def_readwrite("lower_limit",&pyparam_info_info::lower_limit)
    .def_readwrite("upper_limit",&pyparam_info_info::upper_limit)
    ;


  class_<pyfitter_info>("fitter_info",init<>())
    .def_readwrite("model_name",&pyfitter_info::model_name)
    .def_readwrite("statistic_name",&pyfitter_info::statistic_name)
    .def_readwrite("data_set_name",&pyfitter_info::data_set_name)
    .def_readwrite("opt_method_name",&pyfitter_info::opt_method_name)
    .def_readwrite("param_modifier_name",&pyfitter_info::param_modifier_name)
    .def_readwrite("params",&pyfitter_info::params)
    ;
  
}



